Flutter Sembast
介绍
Flutter 下数据库有 Hive、Moor、SQFlite,还有 Sembast。
数据库:将管理良好的结构化信息,以数码方式存储在电子设备上的软件。
关系型数据库:通过表、列和行的形式管理数据,效率最高,扩展性良好。
非关系型(NoSQL):它允许非结构化和半结构化的数据被存储和操作(与关系型数据库相反,关系型数据库定义了所有插入数据库的数据必须如何组成)。随着网络应用变得越来越普遍和复杂,NoSQL数据库逐渐流行起来。
名字的来源:Sembast db stands for Simple Embedded Application Store database.
引用依赖
path_provider: ^1.6.10
sembast: ^2.4.4+3
数据格式
针对的是单进程环境,数据库是一个单文件,在打开时全部载入内存。改动添加到文件的末尾,并会定期对文件进行压缩清理。还支持加密。整个数据库文件是一个 JSON 文件。
整体上是一个 K-V 存储结构,数据库包含 3 个字段:
{“key”:1, ”store”:”StoreName”, ”value”:{}}
其中:
- key:是一个全局自增的主键
- store:数据库支持多数据表,这个字段表示数据对应表名
- value:具体值,是一个 JSON 格式
Model
一个标准的基于原生 Dart 的 Model 定义方式如下:
class Cake {
final int id;
final String name;
final int yummyness;
Cake({this.id, this.name, this.yummyness});
Map<String, dynamic> toMap() {
return {
'name': this.name,
'yummyness': this.yummyness
};
}
factory Cake.fromMap(int id, Map<String, dynamic> map) {
return Cake(
id: id,
name: map['name'],
yummyness: map['yummyness'],
);
}
Cake copyWith({int id, String name, int yummyness}){
return Cake(
id: id ?? this.id,
name: name ?? this.name,
yummyness: yummyness ?? this.yummyness,
);
}
}
这种写法过于繁琐,后来我又学会了基于 built_value 来创建 Entity。
架构模式
学到一种比较好的架构模式。用到的依赖有 get_it、 path_provider、path。
初始化类
首先有一个类专门负责 App 的初始化:
import 'package:get_it/get_it.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
class Init {
static Future initialize() async {
await _initSembast();
}
static Future _initSembast() async {
final appDir = await getApplicationDocumentsDirectory();
await appDir.create(recursive: true);
final databasePath = join(appDir.path, "sembast.db");
final database = await databaseFactoryIo.openDatabase(databasePath);
GetIt.I.registerSingleton<Database>(database);
}
}
其中进行了数据库的创建与初始化,后面有了 Repository 之后也在这里面进行初始化。 getApplicationDocumentsDirectory 是 path_provider 库里面的, join 是 path 库里面的。这里面打开数据库之后,通过 GetIt 把数据库给作为单例存起来了。
创建 Repository
这里比较讲究,先创建了一个抽象类:
import 'cake.dart';
abstract class CakeRepository {
Future<int> insertCake(Cake cake);
Future updateCake(Cake cake);
Future deleteCake(int cakeId);
Future<List<Cake>> getAllCakes();
}
之后创建实现类:
import 'package:get_it/get_it.dart';
import 'package:sembast/sembast.dart';
import 'package:sembast_tutorial/cake.dart';
import 'package:sembast_tutorial/cake_repository.dart';
class SembastCakeRepository extends CakeRepository {
final Database _database = GetIt.I.get();
final StoreRef _store = intMapStoreFactory.store("cake_store");
@override
Future<int> insertCake(Cake cake) async {
return await _store.add(_database, cake.toMap());
}
@override
Future updateCake(Cake cake) async {
await _store.record(cake.id).update(_database, cake.toMap());
}
@override
Future deleteCake(int cakeId) async{
await _store.record(cakeId).delete(_database);
}
@override
Future<List<Cake>> getAllCakes() async {
final snapshots = await _store.find(_database);
return snapshots
.map((snapshot) => Cake.fromMap(snapshot.key, snapshot.value))
.toList(growable: false);
}
}
为什么要使用这种抽象类加实现类的方式?答案是能够支持多种数据源,比如: SembastCakeRepository 、 SQLiteCakeRepository 、 FirebaseCakeRepository 。
扩展 init
static Future initialize() async {
await _initSembast();
_registerRepositories();
}
static _registerRepositories(){
GetIt.I.registerLazySingleton<CakeRepository>(() => SembastCakeRepository());
}
主 App FutureBuilder
import 'package:flutter/material.dart';
import 'package:sembast_tutorial/home_page.dart';
import 'package:sembast_tutorial/init.dart';
void main() => runApp(CakeApp());
class CakeApp extends StatefulWidget {
@override
_CakeAppState createState() => _CakeAppState();
}
class _CakeAppState extends State<CakeApp> {
final Future _init = Init.initialize();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Favorite Cakes',
home: FutureBuilder(
future: _init,
builder: (context, snapshot){
if (snapshot.connectionState == ConnectionState.done){
return HomePage();
} else {
return Material(
child: Center(
child: CircularProgressIndicator(),
),
);
}
},
),
);
}
}